package com.hoas.api.encrypt.type.rsa;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;


/**
 * Created by shenxy on 2016/12/19.
 */
public class RSAEncrypt {

	public static final String KEY_ALGORITHM = "RSA";

	/**
	 * 签名算法
	 */
	public static final String SIGN_ALGORITHMS = "SHA1WithRSA";

	public static final int KEY_INIT_SIZE = 1024;
	public static final String UTF8 = "UTF-8";



	/**
	 * RSA最大加密明文大小
	 */
	private static final int MAX_ENCRYPT_BLOCK = 117;

	/**
	 * RSA最大解密密文大小
	 */
	private static final int MAX_DECRYPT_BLOCK = 128;
	/**
	 * 取得公钥
	 *
	 * @param keyPair
	 * @return
	 * @throws Exception
	 */
	public static String getPublicKey(KeyPair keyPair) throws Exception {
		RSAPublicKey publicKeyObj = (RSAPublicKey) keyPair.getPublic();
		byte[] publicKey = publicKeyObj.getEncoded();
		return encryptBASE64(publicKey);
	}

	/**
	 * 取得私钥
	 *
	 * @param keyPair
	 * @return
	 * @throws Exception
	 */
	public static String getPrivateKey(KeyPair keyPair) throws Exception {
		RSAPrivateKey privateKeyObj = (RSAPrivateKey) keyPair.getPrivate();
		byte[] privateKey =privateKeyObj.getEncoded();
		return encryptBASE64(privateKey);
	}



	/**
	 * BASE64解密
	 *
	 * @param key
	 * @return
	 * @throws Exception
	 */
	public static byte[] decryptBASE64(String key) throws Exception {
		return Base64.decodeBase64(key);
	}

	/**
	 * BASE64加密
	 *
	 * @param key
	 * @return
	 * @throws Exception
	 */
	public static String encryptBASE64(byte[] key) throws Exception {
		return Base64.encodeBase64String(key);
	}


	/**
	 * 公钥BASE64加密后转成公钥对象
	 *
	 * @param pubStr
	 * @return
	 * @throws Exception
	 */

	public static RSAPublicKey string2PublicKey(String pubStr) throws Exception {

		try {
			byte[] keyBytes = decryptBASE64(pubStr);

			X509EncodedKeySpec KeySpec = new X509EncodedKeySpec(keyBytes);
			KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
			RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(KeySpec);
			return publicKey;
		}catch (NoSuchAlgorithmException e) {
			throw new Exception("无此算法");
		} catch (InvalidKeySpecException e) {
			throw new Exception("公钥非法");
		} catch (NullPointerException e) {
			throw new Exception("公钥数据为空");
		}
	}

	/**
	 * 私钥BASE64加密后转成私钥对象
	 *
	 * @param priStr
	 * @return
	 * @throws Exception
	 */
	public static RSAPrivateKey string2PrivateKey(String priStr) throws Exception {

		try {
			byte[] keyBytes = decryptBASE64(priStr);

			PKCS8EncodedKeySpec KeySpec = new PKCS8EncodedKeySpec(keyBytes);
			KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
			RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(KeySpec);
			return privateKey;
		}catch (NoSuchAlgorithmException e) {
			throw new Exception("无此算法");
		} catch (InvalidKeySpecException e) {
			throw new Exception("私钥非法");
		} catch (NullPointerException e) {
			throw new Exception("私钥数据为空");
		}
	}

	/**
	 * byte数组转为string
	 *
	 * @param encrytpByte
	 * @return
	 */
	public String bytesToString(byte[] encrytpByte) {
		String result = "";
		for (Byte bytes : encrytpByte) {
			result += (char) bytes.intValue();
		}
		return result;
	}

	/**
	 * 加密方法
	 *
	 * @param publicKey
	 * @param data
	 * @return
	 */
	public static byte[] encrypt(PublicKey publicKey, byte[] data) throws Exception {
		if (publicKey != null) {
			try {
				Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
				cipher.init(Cipher.ENCRYPT_MODE, publicKey);

				int inputLen = data.length;
				if( MAX_ENCRYPT_BLOCK > inputLen )  {
					return cipher.doFinal(data);
				}

				//长明文加密
				ByteArrayOutputStream out = null;
				try {
					out = new ByteArrayOutputStream();
					int offSet = 0;
					byte[] cache;
					int i = 0;
					// 对数据分段加密
					while (inputLen - offSet > 0) {
						if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
							cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
						} else {
							cache = cipher.doFinal(data, offSet, inputLen - offSet);
						}
						out.write(cache, 0, cache.length);
						i++;
						offSet = i * MAX_ENCRYPT_BLOCK;
					}

					return out.toByteArray();

				} finally {
					if(out != null) {
						out.close();
					}
				}



			} catch (NoSuchPaddingException e) {
				e.printStackTrace();
				return null;
			}catch (IllegalBlockSizeException e) {
				throw new Exception("明文长度非法");
			}
		}else {
			throw new Exception("加密公钥为空, 请设置");
		}
	}

	/**
	 * 私钥加密过程
	 *
	 * @param privateKey
	 *            私钥
	 * @param plainTextData
	 *            明文数据
	 * @return
	 * @throws Exception
	 *             加密过程中的异常信息
	 */
	public static byte[] encrypt(PrivateKey privateKey, byte[] plainTextData)
			throws Exception {
		if (privateKey == null) {
			throw new Exception("加密私钥为空, 请设置");
		}
		Cipher cipher = null;
		try {
			// 使用默认RSA
			cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.ENCRYPT_MODE, privateKey);
			byte[] output = cipher.doFinal(plainTextData);
			return output;
		} catch (NoSuchAlgorithmException e) {
			throw new Exception("无此加密算法");
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
			return null;
		} catch (InvalidKeyException e) {
			throw new Exception("加密私钥非法,请检查");
		} catch (IllegalBlockSizeException e) {
			throw new Exception("明文长度非法");
		} catch (BadPaddingException e) {
			throw new Exception("明文数据已损坏");
		}
	}

	/**
	 * 解密方法
	 *
	 * @param privateKey
	 * @param data
	 * @return
	 */
	public static byte[] decrypt(PrivateKey privateKey, byte[] data) throws Exception {

		if (privateKey != null) {
			try {
				Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
				cipher.init(Cipher.DECRYPT_MODE, privateKey);

				int inputLen = data.length;
				if ( MAX_DECRYPT_BLOCK > inputLen) {
					return cipher.doFinal(data);
				}

				ByteArrayOutputStream out = null;
				int offSet = 0;
				byte[] cache;
				int i = 0;

				try {

					out = new ByteArrayOutputStream();

					// 对数据分段解密
					while (inputLen - offSet > 0) {
						if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
							cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
						} else {
							cache = cipher.doFinal(data, offSet, inputLen - offSet);
						}
						out.write(cache, 0, cache.length);
						i++;
						offSet = i * MAX_DECRYPT_BLOCK;
					}
					return out.toByteArray();
				} finally {
					if(out != null) {
						out.close();
					}
				}

			} catch (NoSuchPaddingException  e) {
				e.printStackTrace();
				return null;
			}catch (InvalidKeyException e) {
				throw new Exception("解密私钥非法,请检查");
			} catch (IllegalBlockSizeException e) {
				throw new Exception("密文长度非法");
			} catch (BadPaddingException e) {
				throw new Exception("密文数据已损坏");
			}
		}else {
			throw new Exception("解密私钥为空, 请设置");
		}

	}

	/**
	 * 公钥解密过程
	 *
	 * @param publicKey
	 *            公钥
	 * @param cipherData
	 *            密文数据
	 * @return 明文
	 * @throws Exception
	 *             解密过程中的异常信息
	 */
	public static byte[] decrypt(PublicKey publicKey, byte[] cipherData)
			throws Exception {
		if (publicKey == null) {
			throw new Exception("解密公钥为空, 请设置");
		}
		Cipher cipher = null;
		try {
			// 使用默认RSA
			cipher = Cipher.getInstance("RSA");
			// cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());
			cipher.init(Cipher.DECRYPT_MODE, publicKey);
			byte[] output = cipher.doFinal(cipherData);
			return output;
		} catch (NoSuchAlgorithmException e) {
			throw new Exception("无此解密算法");
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
			return null;
		} catch (InvalidKeyException e) {
			throw new Exception("解密公钥非法,请检查");
		} catch (IllegalBlockSizeException e) {
			throw new Exception("密文长度非法");
		} catch (BadPaddingException e) {
			throw new Exception("密文数据已损坏");
		}
	}

	/**
	 * @Title:	decodePassword
	 * @Description: 用密钥解密
	 * @param privateKey 密钥
	 * @param password 加密密码
	 * @return
	 * @throws Exception  String
	 * @author zym
	 * @date 2016-12-22 下午3:00:21
	 */
	public static String decodePassword(String privateKey, String password) throws Exception {
		byte[] aa = decryptBASE64(password);
		byte[] de = decrypt(string2PrivateKey(privateKey), aa);
		return new String(de,"UTF-8");
	}


	/**
	 * RSA签名
	 * @param content 待签名数据
	 * @param privateKey 商户私钥
	 * @param encode 字符集编码
	 * @return 签名值
	 */
	public static String sign(String content, String privateKey, String encode)
	{
		try
		{
			PKCS8EncodedKeySpec priPKCS8 	= new PKCS8EncodedKeySpec( decryptBASE64(privateKey) );

			KeyFactory keyf 				= KeyFactory.getInstance("RSA");
			PrivateKey priKey 				= keyf.generatePrivate(priPKCS8);

			Signature signature = Signature.getInstance(SIGN_ALGORITHMS);

			signature.initSign(priKey);
			signature.update( content.getBytes(encode));

			byte[] signed = signature.sign();

			return encryptBASE64(signed);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		return null;
	}

	public static String sign(String content, String privateKey)
	{
		try
		{
			PKCS8EncodedKeySpec priPKCS8 	= new PKCS8EncodedKeySpec( decryptBASE64(privateKey) );
			KeyFactory keyf = KeyFactory.getInstance("RSA");
			PrivateKey priKey = keyf.generatePrivate(priPKCS8);
			Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
			signature.initSign(priKey);
			signature.update( content.getBytes());
			byte[] signed = signature.sign();
			return encryptBASE64(signed);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * RSA验签名检查
	 * @param content 待签名数据
	 * @param sign 签名值
	 * @param publicKey 分配给开发商公钥
	 * @param encode 字符集编码
	 * @return 布尔值
	 */
	public static boolean verify(String content, String sign, String publicKey,String encode)
	{
		try
		{
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			byte[] encodedKey = decryptBASE64(publicKey);
			PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));


			Signature signature = Signature
					.getInstance(SIGN_ALGORITHMS);

			signature.initVerify(pubKey);
			signature.update( content.getBytes(encode) );

			boolean bverify = signature.verify( decryptBASE64(sign) );
			return bverify;

		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		return false;
	}

	public static boolean verify(String content, String sign, String publicKey)
	{
		try
		{
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			byte[] encodedKey = decryptBASE64(publicKey);
			PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));


			Signature signature = Signature
					.getInstance(SIGN_ALGORITHMS);

			signature.initVerify(pubKey);
			signature.update( content.getBytes() );

			boolean bverify = signature.verify( decryptBASE64(sign) );
			return bverify;

		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		return false;
	}

	public static Map<String, String> gen(){

		KeyPairGenerator keyPairGen = null;
		try {
			keyPairGen = KeyPairGenerator.getInstance("RSA");

			keyPairGen.initialize(KEY_INIT_SIZE);
			KeyPair keyPair = keyPairGen.generateKeyPair();

			String pub = getPublicKey(keyPair);

			String pri = getPrivateKey(keyPair);

			Map<String,String> keypairs = new HashMap<>();
			keypairs.put( "publicKey", pub);
			keypairs.put( "privateKey", pri);

			return  keypairs;

		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;

	}


}
